Conversation
* feat(mcp): JSON-RPC endpoint skeleton for MCP Apps
* feat(mcp): mount /mcp handler in app handler chain when enabled
* feat(mcp): postMessage tunnel connection and _shiny_* tools
* feat(mcp): ui://shiny/app single-file resource with inlined assets
* feat(mcp): postMessage bridge bundle overriding Shiny.createSocket
* fix(mcp): use .globals for sweeper flag (assignInMyNamespace fails in later callback)
* docs(mcp): NEWS entry for experimental MCP Apps support
* docs(mcp): mirror MCP Apps plans, research notes, and demo app into mcp/
* feat(mcp): _shiny_http tool tunnels HTTP side channels in-process
* feat(mcp): inline dynamic htmlDependencies for MCP tunnel sessions
* feat(mcp): tunnel XHR side channels, downloads, host theme + context (Phase 2 client)
* docs(mcp): Phase 2 NEWS + mirror plan/demo/screenshot into mcp/
* feat(mcp): inline CSS url() assets as data URIs for the sandbox
* feat(mcp): exported session API — mcpToolInput, mcpUpdateModelContext, mcpSendMessage, mcpHostContext, isMcpSession
* feat(mcp): author-declared MCP tools via options(shiny.mcp.tools)
* feat(mcp): stdio transport for local desktop hosts
* feat(mcp): direct-connect WebSocket fast path + display mode requests
* docs(mcp): direct-connect/display-mode NEWS, mcptools upstream notes, QUESTIONS.md
* docs(mcp): use real PR number in NEWS
* docs(mcp): record decisions in QUESTIONS.md; explain direct-connect default
* fix(mcp): declare processx/pkgload test deps; run stdio subprocess against installed shiny under R CMD check
* feat(mcp): path-aware direct-connect base (shiny.mcp.origin, X-RSC-Request, rsconnect records)
* feat(mcp): discover deployed URL from Quarto _publish.yml and Posit Publisher records
* fix: pkgdown index for mcp-session; wrap-tolerant reactlog Suggests test; drop Quarto _publish.yml source
* `npm run build` (GitHub Actions)
* fix(mcp): use RStudio-Connect-App-Base-Url for the direct-connect base
Verified against a real connect.posit.it deployment: Connect does not
send X-RSC-Request to Shiny content, and rsconnect excludes its own
rsconnect/ records from the bundle. The header Connect actually sends
with every proxied request is RStudio-Connect-App-Base-Url, which is
already the app's external base URL. Honor it first (X-RSC-Request kept
for API content).
* feat(mcp): shinyapps.io direct-connect base via X-Redx-Frontend-Name
Verified against a real shinyapps.io deployment: the proxy passes
POST /mcp through unauthenticated, sends RStudio-Connect-App-Base-Url
with an EMPTY value, and identifies the external base as
X-Redx-Frontend-Name (schemeless host + path). Combine that with
X-Forwarded-Proto for the direct base, and prefer X-Forwarded-Host
over the internal Host for the generic origin fallback and
deployment-record host matching.
* feat(mcp): appId namespacing so a gateway can serve multiple apps
options(shiny.mcp.appId=) prefixes the internal _shiny_* tunnel tools
(demo_shiny_connect, ...) and publishes the UI resource as
ui://shiny/<appId>; the bridge reads appId from __shinyMcpConfig__ and
prefixes its tool calls to match. mcp/gateway/shiny-mcp-gateway.mjs
(zero-dep Node, stdio or HTTP) merges several app endpoints into one MCP
server, so a single connector exposes many Shiny apps. E2E verified in
basic-host with two apps behind one gateway, including full tunnel
reactivity.
* docs(mcp): add mcp/architecture.md describing the system as built
* docs(mcp): compare with JamesHWade/shinymcp
* fix(mcp/gateway): cookie stickiness, cold-start wake retry, drain on close
shinyapps.io rejects POST to a sleeping app with 405 (only GET
cold-starts an instance) and pins clients to an instance via a session
cookie. The gateway now keeps a per-backend cookie jar, and on a failed
POST wakes the app with a GET to its base URL and retries with backoff.
Also drain in-flight requests before exiting when stdin closes.
* chore(mcp): commit demo-app deployment records (no secrets; exercise the DCF direct-base path)
* fix(mcp): inline render-time deps from direct createWebDependency() calls
{htmlwidgets} (and friends) call createWebDependency() directly for the
dependencies attached to a widget's render payload, bypassing the
processDeps() MCP hook — so a dynamically shown plotly widget arrived
with URL-based crosstalk/CSS deps that 404 inside the sandbox
("crosstalk is not defined"). createWebDependency() now diverts
file-based deps to mcpInlineDependency() when the current reactive
domain is an MCP session; href-only deps fall through to avoid
recursion. Demo: cars app gains a checkbox-gated plotly plot,
E2E-verified in basic-host (Plotly + crosstalk load on demand, 32
points rendered).
* test(mcp): write download bytes with writeBin for Windows
writeLines() opens the file path in text mode, so on Windows the CSV's
\n bytes became \r\n and the _shiny_http download body assertion failed
(only Windows R-CMD-check was red). writeBin(charToRaw(...)) writes the
exact bytes on every platform.
* chore: ignore .playwright-mcp scratch dir (git + R build)
---------
Co-authored-by: schloerke <schloerke@users.noreply.github.com>
…() (#4412) Replace options(shiny.mcp.tools=) with registerMcpTool(), accepting ellmer::tool() objects; convert to MCP tools/list schema via the mcptools as_json() path and invoke on tools/call with rlang::exec(). ellmer -> Suggests. ToolDef props read via S7::prop() for R < 4.3. Docs, demos, and mcp/ architecture updated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch
mcp→mainfor the experimental MCP Apps support. The feature was developed and reviewed on #4407 (squash-merged intomcp); this PR promotes it tomain.Summary
Adds experimental support for serving a Shiny app as an MCP App (SEP-1865): with
options(shiny.mcp = TRUE)an app mounts a Model Context Protocol endpoint at/mcpand renders live and reactive inside an MCP host's conversation (Claude Desktop, claude.ai, VS Code Copilot, etc.).How it works, in brief:
options(shiny.mcp.stdio = TRUE)), both through one dispatcher.ui://shiny/appresource is the rendered page as a single self-contained HTML document (assets inlined, CSSurl()→data:URIs). A JS bridge overridesShiny.createSocket: it opens a real WebSocket where the host honors the declared CSP (direct fast path), otherwise it tunnels Shiny's websocket overtools/call(long-poll for server→client). HTTP side channels (upload/download, server-side selectize/DataTables, dynamically inserted htmlDependencies) tunnel through an app-only_shiny_httptool.isMcpSession(),mcpToolInput(),mcpHostContext(),mcpUpdateModelContext(),mcpSendMessage(),mcpRequestDisplayMode(). Author-declared model-callable tools viaoptions(shiny.mcp.tools=).RStudio-Connect-App-Base-Url) and shinyapps.io (X-Redx-Frontend-Name), falling back to deployment records / request origin.options(shiny.mcp.appId=)namespaces each app's internal tools and resource URI; a small Node gateway (mcp/gateway/) merges several app endpoints into a single MCP server.Design and working docs (E2E-ignored from the package build) live under
mcp/— seemcp/architecture.mdfor the full "system as built" write-up.Verification
R CMD checkmatrix green (all Ubuntu 4.2→devel, macOS, Windows), plusroutineandpkgdown.claude/Claude Desktop.Known open item
Experimental (
lifecycle::badge("experimental")); not enabled unlessoptions(shiny.mcp = TRUE).